package com.fzy.springsecurity2.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fzy.springsecurity2.bean.TUser;
import com.fzy.springsecurity2.dto.R;
import com.fzy.springsecurity2.security.JsonAuthenticationFilter;
import com.fzy.springsecurity2.security.JwtAuthenticationFilter;
import com.fzy.springsecurity2.service.TUserService;
import com.fzy.springsecurity2.utils.JwtUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.util.StringUtils;

import java.io.IOException;

/**
 * @author fzy
 * @version 1.0
 * 创建时间：2024-10-08 16:57
 */

@Slf4j
@Configuration
@EnableMethodSecurity   // 开启方法级别的权限控制
public class WebSecurityConfig {

    @Resource
    TUserService tUserService;

    @Resource
    RedisTemplate<String, Object> redisTemplate;

    @Resource
    ObjectMapper objectMapper;


    /**
     * 自行实现 UserDetailsService，实现获取用户信息的逻辑
     * Username -》 UserDetails
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            final TUser user = tUserService.lambdaQuery().eq(TUser::getName, username).one();
            if (user == null) throw new UsernameNotFoundException("用户名未注册");
            return user;
        };
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, JsonAuthenticationFilter jsonAuthenticationFilter)
            throws Exception {
        http
                .authorizeHttpRequests(request -> {
                        request.requestMatchers("/tuser/login").permitAll()
                                .requestMatchers("/tuser/*").hasAuthority("ROLE_USER")
                                .anyRequest().authenticated();
                })
                .addFilterAfter(new JwtAuthenticationFilter(), SecurityContextHolderFilter.class)
                //.addFilterBefore(new JwtAuthenticationFilter(), JsonAuthenticationFilter.class)
                //.addFilterAt(jsonAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .logout(logout -> {
                        logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
                })
                .exceptionHandling(exceptionHandling -> {
                            exceptionHandling.authenticationEntryPoint(new MyAuthenticationEntryPoint())
                                    .accessDeniedHandler(new MyAccessDeniedHandler());
                })
                .sessionManagement((session) -> {
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                })
                .csrf(csrf -> {
                    csrf.disable();
                })
                .cors(cors -> {

                })
        ;
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public JsonAuthenticationFilter jsonAuthenticationFilter(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        JsonAuthenticationFilter jsonAuthenticationFilter = new JsonAuthenticationFilter(authenticationConfiguration.getAuthenticationManager());
        // 指定登录接口
        jsonAuthenticationFilter.setFilterProcessesUrl("/tuser/login");
        jsonAuthenticationFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
        jsonAuthenticationFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
        // 指定 json 数据中，用户名密码所在的字段
        jsonAuthenticationFilter.setUsernameParameter("name");
        jsonAuthenticationFilter.setPasswordParameter("password");
        // 指定 SecurityContextRepository
        jsonAuthenticationFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
        return jsonAuthenticationFilter;
    }

    @Bean
    static RoleHierarchy roleHierarchy() {
        var hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
                "ROLE_STAFF > ROLE_USER\n" +
                "ROLE_USER > ROLE_GUEST");
        return hierarchy;
    }

    // and, if using method security also add
    @Bean
    static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy);
        return expressionHandler;
    }




    class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            // 将用户 id 封装进 jwt
            String jwt = JwtUtil.createJWT(objectMapper.writeValueAsString(((TUser)authentication.getPrincipal()).getId()));
            // 将 jwt 写入响应头中的 Authorization 字段
            response.setHeader("Authorization", jwt);
            response.getWriter().write(objectMapper.writeValueAsString(R.success("登录成功")));

            // 自定义缓存用户信息逻辑
            // 这里选择将用户id 作为 key，用户信息作为 value，存入 redis。
            Object o =  authentication.getPrincipal();
            if(o instanceof TUser user){
                user.setPassword(null);  // 信息脱敏
                String json = objectMapper.writeValueAsString(user);
                redisTemplate.opsForValue().set(String.valueOf(user.getId()), json, RedisConfig.DEFAULT_REDIS_EXPIRE);
            } else {
                log.error("在试图缓存用户信息时，获取到的用户信息不是 TUser 类型");
            }

        }
    }

    static class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().write(objectMapper.writeValueAsString(R.error(exception.getMessage())));
        }
    }

    class MyLogoutSuccessHandler implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            String jwt = request.getHeader(JwtUtil.JWT_HEADER_FIELD);
            String id = null;
            try {
                id = JwtUtil.parseJWT(jwt).getSubject();
                redisTemplate.delete(id);
            } catch (Exception e) {
                if (!StringUtils.hasText(jwt) || !StringUtils.hasText(id)) {
                    response.getWriter().write(objectMapper.writeValueAsString(R.error("未登录")));
                    return;
                }
                response.getWriter().write(objectMapper.writeValueAsString(R.error("注销失败")));
                return;
            }
            response.getWriter().write(objectMapper.writeValueAsString(R.success("注销成功")));
        }
    }

    static class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().write(objectMapper.writeValueAsString(R.error("未登录")));
        }
    }

    static class MyAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().write(objectMapper.writeValueAsString(R.error("权限不足")));
        }
    }
}
